﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using CSW.Framework.Common;
using IEnumerator=System.Collections.IEnumerator;

namespace CSW.Framework.Linq
{
    internal class TableQueryObjectProvider<T, TResult> : IQueryable<TResult>
        where T : class, IBusinessObjectLinq, new()
    {
        private readonly Expression m_Expression;
        private readonly IQueryProvider m_QueryProvider;

        public TableQueryObjectProvider(Expression expression, IQueryProvider queryProvider)
        {
            Guard.ArgumentNotNull(expression, "expression");
            Guard.ArgumentNotNull(queryProvider, "queryProvider");

            m_Expression = expression;
            m_QueryProvider = queryProvider;
        }

        public IEnumerator<TResult> GetEnumerator()
        {
            MethodCallExpression m = (MethodCallExpression)m_Expression;

            TableQueryProvider<T> qo = null;
            if (m.Arguments[0] is ConstantExpression)
            {
                qo = (TableQueryProvider<T>)((ConstantExpression)m.Arguments[0]).Value;
            }
            else if (m.Arguments[0] is UnaryExpression)
            {
                // TODO: what's going on here
                UnaryExpression qoU = (UnaryExpression)m.Arguments[0];
            }
            else if (m.Arguments[0] is MethodCallExpression)
            {
                // TODO: what's going on here
                MethodCallExpression qoM = (MethodCallExpression)m.Arguments[0];
            }

            UnaryExpression quote = (UnaryExpression)m.Arguments[1];

            Expression<Func<T, TResult>> funcEx = (Expression<Func<T, TResult>>)quote.Operand;
            //Func<T, TResult> func = ((Expression<Func<T, TResult>>)quote.Operand).Compile();
            List<TResult> tuples = new List<TResult>();
            foreach (T obj in qo)
            {
                try
                {
                    tuples.Add(FriendlyEvaluate(funcEx, obj));
                }
                catch (Exception ex)
                {
                    Trace.WriteLine(ex);
                    throw;
                }
            }
            return tuples.GetEnumerator();
        }

        private static TResult DefaultEvaluate(Expression<Func<T, TResult>> funcEx, T obj)
        {
            Guard.ArgumentNotNull(funcEx, "funcEx");
            Guard.ArgumentNotNull(obj, "obj");

            Func<T, TResult> func = funcEx.Compile();
            return func.Invoke(obj);
        }

        private static TResult FriendlyEvaluate(Expression<Func<T, TResult>> funcEx, T obj)
        {
            Guard.ArgumentNotNull(funcEx, "funcEx");
            Guard.ArgumentNotNull(obj, "obj");

            if (!(funcEx.Body is NewExpression))
            {
                return DefaultEvaluate(funcEx, obj);
            }

            ConstructorInfo[] ciList = typeof(TResult).GetConstructors();
            ConstructorInfo ci = ciList[0];
            ParameterInfo[] piList = ci.GetParameters();
            NewExpression bodyExpression = (NewExpression)funcEx.Body;
            
            object[] param = new object[piList.Length];
            for (int i = 0; i < piList.Length; i++)
            {
                Expression arg = bodyExpression.Arguments[i];

                if (arg is MemberExpression)
                {
                    param[i] = ExpandMemberAccess((MemberExpression)arg, obj);
                }
                else if (arg is ParameterExpression)
                {
                    if (arg.Type == typeof(T))
                        param[i] = obj;
                    else
                        return DefaultEvaluate(funcEx, obj);
                }
                else
                {
                    return DefaultEvaluate(funcEx, obj);
                }
                
            }
            
            object objNewObj = ci.Invoke(param);
            TResult newObj = (TResult)objNewObj;
            
            return newObj;
        }

        private static object ExpandMemberAccess(MemberExpression memEx, object obj)
        {
            MemberInfo member = memEx.Member;
            Expression e = memEx.Expression;
            ExpressionType? nodeType = (e == null ? (ExpressionType?)null : e.NodeType);

            if (nodeType == ExpressionType.Parameter)
            {
                string propertyName = member.Name;
                object value = obj.GetType().GetProperty(propertyName).GetGetMethod().Invoke(obj, null);
                return value;
            }
            else
            {
                object memberValue = ExpandMemberAccess((MemberExpression)e, obj);

                if (memberValue == null)
                {
                    return null;
                }
                else
                {
                    string propertyName = member.Name;
                    object value = memberValue.GetType().GetProperty(propertyName).GetGetMethod().Invoke(memberValue, null);
                    return value;
                }
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        // Properties
        public Type ElementType
        {
            get
            {
                return typeof(T);
            }
        }

        public Expression Expression
        {
            get
            {
                return m_Expression;
            }
        }

        public IQueryProvider Provider
        {
            get
            {
                return null;
            }
        }

        Type IQueryable.ElementType
        {
            get
            {
                return typeof(T);
            }
        }

        Expression IQueryable.Expression
        {
            get
            {
                return m_Expression;
            }
        }

        IQueryProvider IQueryable.Provider
        {
            get
            {
                return m_QueryProvider;
            }
        }
    }
}
